﻿using System;
using System.Drawing;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace UDPChatRoomApp
{
    public partial class UDPChatRoom : Form
    {
        private UdpClient receiveClient;                // 接收用
        private Thread receiveThread;                   // 接收消息线程
        private UdpClient sendClient;                   // 发送用
        private Thread sendThread;                      // 发送消息线程
        private string msgReceived;                     // 接收到的消息
        private const int udpPort = 64000;              // 监听广播端口
        private const int tcpFilePort = 64001;          // 文件传输端口
        private const int tcpChatPort = 64002;          // 消息端口
        private string nickName;                        // 用户昵称
        private string fileSavePath;                    // 文件存储路径
        private string fileName;                        // 待接收文件，将来改成数组以满足同时接收多个文件

        public UDPChatRoom()
        {
            InitializeComponent();
            btnSend.Enabled = false;                    // 输入昵称后才允许发送消息
            this.btnSendFile.Enabled = false;           // 必须先选择待发送的文件
            this.btnLogout.Enabled = false;             // 还没登录，注销按钮没有意义
        }

        private void UDPChatRoom_Load(object sender, EventArgs e)
        {
            this.tbNickName.Focus();                                        // 默认焦点，输入昵称
            this.tbNickName.SelectAll();
            Thread t = new Thread(ListenFileRequest);
            t.SetApartmentState(ApartmentState.STA);
            t.Start();
            notifyicon.Visible = false;
        }

        #region 接收消息线程
        private void ReceiveMsg()
        {          
            IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, 0);
            while (true)
            {
                try
                {
                    //关闭udpClient时此句会产生异常  
                    byte[] receiveBytes = receiveClient.Receive(ref endpoint);
                    msgReceived = Encoding.Unicode.GetString(receiveBytes, 0, receiveBytes.Length);
                    rtbDispMsg.AppendText(msgReceived+"\n\n");
                }
                catch
                {
                    break;
                }
            }
        }
        #endregion

        private void btnClear_Click(object sender, EventArgs e)
        {
            tbSendMsg.Clear();
        }

        #region 点击“发送”按钮，发送消息
        private void btnSend_Click(object sender, EventArgs e)
        {
            sendThread = new Thread(BroadcastMsg);
            sendThread.IsBackground = true;
            sendThread.Start(tbSendMsg.Text);
        }
        #endregion

        #region 广播消息线程
        private void BroadcastMsg(object obj)
        {
            string message = (string)obj;
            sendClient = new UdpClient(new IPEndPoint(IPAddress.Any, 0));
            string time = DateTime.Now.ToString("HH:mm:ss");
            byte[] bytes = Encoding.Unicode.GetBytes(time + "," + nickName + ": "+ message);
            IPEndPoint endpoint = new IPEndPoint(IPAddress.Broadcast, udpPort);
            sendClient.Send(bytes, bytes.Length, endpoint);
            tbSendMsg.Clear();
            tbSendMsg.Focus();
        }
        #endregion

        #region 按键监听，实现回车键发送消息
        private void tbSendMsg_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Enter)
            {
                btnSend_Click(sender, e);                   // 触发点击“发送”按钮事件
            }
        }
        #endregion

        #region 点击登录按钮，建立UDP监听
        private void btnJoin_Click(object sender, EventArgs e)
        {
            if(string.IsNullOrWhiteSpace(this.tbNickName.Text))
            {
                MessageBox.Show("请不要使用空昵称");
                this.tbNickName.Clear();
                this.tbNickName.Focus();
            }
            else
            {
                nickName = this.tbNickName.Text;
                this.btnSend.Enabled = true;                                                // 开启发送键
                this.tbSendMsg.Focus();                                                     // 获取消息编辑框焦点
                this.tbSendMsg.KeyDown += new KeyEventHandler(tbSendMsg_KeyDown);           // 允许回车键发送
                receiveClient = new UdpClient(new IPEndPoint(IPAddress.Any, udpPort));      // 创建接收服务
                receiveThread = new Thread(ReceiveMsg);
                receiveThread.IsBackground = true;
                receiveThread.Start();
                sendClient = new UdpClient(new IPEndPoint(IPAddress.Any, 0));
                string time = DateTime.Now.ToString("HH:mm:ss");
                byte[] bytes = Encoding.Unicode.GetBytes(time + "," + nickName + "进入聊天室。");
                IPEndPoint endpoint = new IPEndPoint(IPAddress.Broadcast, udpPort);
                sendClient.Send(bytes, bytes.Length, endpoint);
                this.tbNickName.Enabled = false;
                this.btnJoin.Enabled = false;
            }           
        }
        #endregion

        #region 按键监听，实现回车键登录
        private void tbNickName_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Enter)
            {
                btnJoin_Click(sender, e);                       // 触发点击“登录”按钮事件
            }
        }
        #endregion

        #region 点击选择文件按钮事件
        private void btnChooseFile_Click(object sender, EventArgs e)
        {
            OpenFileDialog fileDialog = new OpenFileDialog();
            fileDialog.Filter = "All files|*.*|Image files|*.jpg;*.png|Word files|*.docx;*.doc|PDF files|*.pdf";
            fileDialog.RestoreDirectory = true;
            if (fileDialog.ShowDialog() == DialogResult.OK)
            {
                this.tbFileInfo.Text = fileDialog.FileName;
                this.btnSendFile.Enabled = true;
            }
                
        }
        #endregion

        #region 点击“发送文件”按钮，发起文件传输请求
        private void btnSendFile_Click(object sender, EventArgs e)
        {
            this.btnSend.Enabled = true;
            Thread sendFile = new Thread(SendRequest);
            sendFile.Start();
        }
        #endregion

        #region 发送文件请求线程
        private void SendRequest()
        {
            IPAddress targetIp = IPAddress.Parse(tbHost.Text.Trim());
            IPEndPoint ipep = new IPEndPoint(targetIp, tcpChatPort);
            Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            try
            {
                client.Connect(ipep);
            }
            catch
            {
                MessageBox.Show("无法连接目标主机！");
                return;
            }

            // 发送消息
            FileInfo fileinfo = new FileInfo(tbFileInfo.Text);
            FileTransfer.SendVarData(client, Encoding.Unicode.GetBytes(fileinfo.Name));
            byte[] buffer = new byte[128];
            int recvNum;
            while (true)
            {
                recvNum = client.Receive(buffer);
                if(recvNum>0)
                {
                    if (BitConverter.ToBoolean(buffer, 0))
                    {
                        Thread t = new Thread(TaskSendFile);
                        t.IsBackground = true;
                        t.Start();
                        client.Close();
                        break;
                    }
                    else
                    {
                        client.Close();
                        MessageBox.Show("文件传输请求被拒绝.");
                        break;
                    }
                }
            }                                       
        }
        #endregion

        #region 传输文件线程
        private void TaskSendFile()
        {
            IPAddress targetIp = IPAddress.Parse(tbHost.Text.Trim());
            IPEndPoint ipep = new IPEndPoint(targetIp, tcpFilePort);
            Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            try
            {
                client.Connect(ipep);
            }
            catch
            {
                MessageBox.Show("无法连接目标主机！");
                return;
            }
            
            FileInfo fileInfo = new FileInfo(this.tbFileInfo.Text);
            FileStream fs = fileInfo.OpenRead();
            long pkgSize = 1024;
            long pkgCount = fs.Length / pkgSize;
            long LastpkgSize = fs.Length % pkgSize;
            
            //FileTransfer.SendVarData(client, Encoding.Unicode.GetBytes(fileInfo.Name));
            FileTransfer.SendVarData(client, Encoding.Unicode.GetBytes(pkgSize.ToString()));
            FileTransfer.SendVarData(client, Encoding.Unicode.GetBytes(pkgCount.ToString()));
            FileTransfer.SendVarData(client, Encoding.Unicode.GetBytes(LastpkgSize.ToString()));

            byte[] data = new byte[pkgSize];
            for (int i = 0; i < pkgCount; i++)
            {
                //从文件流读取数据并填充数据包  
                fs.Read(data, 0, data.Length);
                //发送数据包  
                FileTransfer.SendVarData(client, data);
                //显示发送数据包的个数  
                //进度条值的显示  
                //this.progressBar1.PerformStep();
            }
            if (LastpkgSize != 0)
            {
                data = new byte[LastpkgSize];
                fs.Read(data, 0, data.Length);
                FileTransfer.SendVarData(client, data);
                //this.progressBar1.Value = this.progressBar1.Maximum;
            }
            client.Close();
            this.btnSend.Enabled = true;
        }
        #endregion

        #region 监听发送文件请求
        private void ListenFileRequest()
        {
            IPEndPoint ipep = new IPEndPoint(IPAddress.Any, tcpChatPort);
            Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            server.Bind(ipep);
            server.Listen(10);
            while (true)
            {
                Socket client = server.Accept();
                IPEndPoint clientep = (IPEndPoint)client.RemoteEndPoint;
                // 获得文件名
                fileName = Encoding.Unicode.GetString(FileTransfer.ReceiveVarData(client));
                // 请求接收方确认
                DialogResult dr = MessageBox.Show(clientep.ToString() + "请求向您发送文件：" + fileName + ",是否接收？", "文件接收确认", MessageBoxButtons.YesNo);
                // 确认文件传输
                if (dr == DialogResult.Yes)
                {
                    #region 使用对话框获取文件存储路径
                    FolderBrowserDialog dialog = new FolderBrowserDialog();
                    dialog.Description = "选择文件保存路径";
                    if (DialogResult.OK == dialog.ShowDialog())
                    {
                        fileSavePath = dialog.SelectedPath;
                    }
                    #endregion
                    // 发送确认答复
                    Thread t = new Thread(TaskReceiveFile);
                    t.IsBackground = true;
                    t.Start();
                    FileTransfer.SendVarData(client, BitConverter.GetBytes(1));             
                }
                else
                {
                    // 拒绝接收文件
                    FileTransfer.SendVarData(client, BitConverter.GetBytes(0));             // 发送拒绝消息
                }
                client.Close();
            }
        }
        #endregion

        #region 文件接收线程
        private void TaskReceiveFile()
        {
            IPEndPoint ipep = new IPEndPoint(IPAddress.Any, tcpFilePort);
            Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            server.Bind(ipep);
            server.Listen(20);
            Socket client = server.Accept();
            server.Close();
            
            #region 文件接收代码片段
            //获取[包的大小]  
            string bagSize = Encoding.Unicode.GetString(FileTransfer.ReceiveVarData(client));
            //MessageBox.Show("包大小" + bagSize);  

            //获取[包的总数量]  
            int bagCount = int.Parse(Encoding.Unicode.GetString(FileTransfer.ReceiveVarData(client)));
            //MessageBox.Show("包的总数量" + bagCount);  

            //获取[最后一个包的大小]  
            string bagLast = Encoding.Unicode.GetString(FileTransfer.ReceiveVarData(client));
            //MessageBox.Show("最后一个包的大小" + bagLast);  

            //创建一个新文件  
            FileStream MyFileStream = new FileStream(fileSavePath + "\\"+ fileName, FileMode.Create, FileAccess.Write);

            //已发送包的个数  
            int SendedCount = 0;

            while (true)
            {
                byte[] data = FileTransfer.ReceiveVarData(client);
                if (data == null || data.Length == 0)
                {
                    break;
                }
                else
                {
                    SendedCount++;
                    //将接收到的数据包写入到文件流对象  
                    MyFileStream.Write(data, 0, data.Length);
                    //显示已发送包的个数  
                    //MessageBox.Show("已发送包个数"+SendedCount.ToString());  
                }
            }
            //关闭文件流  
            MyFileStream.Close();
            //关闭套接字  
            client.Close();
            #endregion
        }
        #endregion

        #region 消息显示框文本改变，自动滚至底部
        private void rtbDispMsg_TextChanged(object sender, EventArgs e)
        {
            rtbDispMsg.ScrollToCaret();
        }
        #endregion
        private void btnLogout_Click(object sender, EventArgs e)
        {

        }

        private void UDPChatRoom_SizeChanged(object sender, EventArgs e)
        {
            if (WindowState == FormWindowState.Minimized)
            {
                //隐藏任务栏区图标 
                this.ShowInTaskbar = false;
                //图标显示在托盘区 
                notifyicon.Visible = true;
            }
        }

        private void notifyicon_DoubleClick(object sender, EventArgs e)
        {
            if (WindowState == FormWindowState.Minimized)
            {
                //还原窗体显示 
                WindowState = FormWindowState.Normal;
                //激活窗体并给予它焦点 
                this.Activate();
                //任务栏区显示图标 
                this.ShowInTaskbar = true;
                //托盘区图标隐藏 
                notifyicon.Visible = false;
            }
        }

        private void UDPChatRoom_FormClosing(object sender, FormClosingEventArgs e)
        {
            e.Cancel = true;
            this.WindowState = FormWindowState.Minimized;
        }

        private void 还原ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            WindowState = FormWindowState.Normal;
        }

        private void 退出ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (MessageBox.Show("是否确认退出程序？", "退出", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) == DialogResult.OK)
            {
                // 关闭所有的线程
                this.Dispose();
                this.Close();
            }
        }
    }
}
